﻿[assembly: System.CLSCompliant(true)]

namespace Hopfield.Math
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Runtime.InteropServices;
    using System.Text;    

    /// <summary>
    /// C# HopfieldSimulator math class.
    /// </summary>
    [CLSCompliant(true)]
    public class Hopfield
    {
        /// <summary>
        /// Initializes a new instance of the Hopfield class.
        /// </summary>
        public Hopfield()
        {
            this.HMC = new HopfieldMathCore();
            this.Colorized = false;
        }

        private static Hopfield instance = null;
        private static object mutex = new object();

        /// <summary>
        /// Singleton instance.
        /// </summary>
        public static Hopfield Instance
        {
            get
            {
                lock (mutex)
                {
                    if (instance == null)
                    {
                        instance = new Hopfield();
                    }

                    return instance;
                }
            }
        }

        /// <summary>
        /// Gets C++ math object property.
        /// </summary>
        public HopfieldMathCore HMC { get; private set; }

        /// <summary>
        /// Gets or sets switch Color (true) vice Gray-Scale.
        /// </summary>
        public bool Colorized { get; set; }

        /// <summary>
        /// Gets output vector.
        /// </summary>
        /// <returns>Outut vector</returns>
        public float[] Y
        {
            get
            {
                unsafe
                {
                    float* f = this.HMC.GetY();
                    int n = this.HMC.Getn();
                    float[] r = new float[n];
                    for (int i = 0; i < n; i++)
                    {
                        r[i] = f[i];
                    }

                    return r;
                }
            }
        }

        /// <summary>
        /// Gets an array of weights.
        /// </summary>
        public float[,] W
        {
            get
            {
                unsafe
                {
                    float** f = this.HMC.GetW();
                    int n = this.HMC.Getn();
                    float[,] r = new float[n, n];
                    for (int i = 0; i < n; i++)
                    {
                        for (int j = 0; j < n; j++)
                        {
                            r[i, j] = f[i][j];
                        }
                    }

                    return r;
                }
            }
        }

        /// <summary>
        /// Gets attractors, first dimension should equal l, second should equal n.
        /// </summary>
        public float[,] L
        {
            get
            {
                unsafe
                {
                    float** f = this.HMC.GetL();
                    int n = this.HMC.Getn();
                    int l = this.HMC.Getl();
                    float[,] r = new float[l, n];
                    for (int i = 0; i < l; i++)
                    {
                        for (int j = 0; j < n; j++)
                        {
                            r[i, j] = f[i][j];
                        }
                    }

                    return r;
                }
            }
        }

        /// <summary>
        /// Gets recognized attractor index of L array.
        /// </summary>
        public int GetRecognizedLIndex(int count, out float mse)
        {
            unsafe
            {
                float mseRec = 0.0f;
                int index = this.HMC.GetRecognizedLIndex(count, &mseRec);
                mse = mseRec;
                return index;
            }
        }

        /// <summary>
        /// Gets number of picture pixels rows.
        /// </summary>
        /// <returns>Number of rows</returns>
        public int Rows
        {
            get
            {
                unsafe
                {
                    return this.HMC.GetRows();
                }
            }
        }

        /// <summary>
        /// Gets number of picture pixels columns.
        /// </summary>
        /// <returns>Number of columns</returns>
        public int Cols
        {
            get
            {
                unsafe
                {
                    return this.HMC.GetCols();
                }
            }
        }

        /// <summary>
        /// Gets number of attracotrs.
        /// </summary>
        public int LCount
        {
            get
            {
                int l = 0;
                unsafe
                {
                    l = this.HMC.Getl();
                }
                return l;
            }
        }

        /// <summary>
        /// Gets resoluton of output values.
        /// </summary>
        /// <returns>Resolution of output values</returns>
        public int D
        {
            get
            {
                unsafe
                {
                    return this.HMC.Getd();
                }
            }
        }

        /// <summary>
        /// Gets number of net neurons.
        /// </summary>
        public int NCount
        {
            get
            {
                unsafe
                {
                    return this.HMC.Getn();
                }
            }
        }

        /// <summary>
        /// Gets calculated energy of network as associative memory.
        /// </summary>
        /// <returns>Energy of associative memory</returns>
        public float Energy
        {
            get
            {
                unsafe
                {
                    return this.HMC.GetE();
                }
            }
        }

        /// <summary>
        /// Recreation of neural net.
        /// </summary>
        /// <param name="n">Number of neurons</param>
        public void Recreate(int n)
        {
            unsafe
            {
                this.HMC.ReCreate(n);
            }
        }

        /// <summary>
        /// Math class can automatically simulate net to this MSE it should be small.
        /// </summary>
        /// <param name="newMSEmin">New value of MSE</param>
        public void SetMSEmin(float newMSEmin)
        {
            unsafe
            {
                this.HMC.SetMSEmin(newMSEmin);
            }
        }

        /// <summary>
        /// Open and load neural network from file.
        /// </summary>
        /// <param name="fname">Name of file to open</param>
        public void OpenHNFile(string fname)
        {
            unsafe
            {
                this.HMC.OpenHNFile((sbyte*)Marshal.StringToCoTaskMemAnsi(fname));
            }
        }

        /// <summary>
        /// Save neural network to file.
        /// </summary>
        /// <param name="fname">Name of file to save</param>
        public void SaveHNFile(string fname)
        {
            unsafe
            {
                this.HMC.SaveHNFile((sbyte*)Marshal.StringToCoTaskMemAnsi(fname));
            }
        }

        /// <summary>
        /// Save output vector of neural net as ASCII format PPM file.
        /// </summary>
        /// <param name="fname">Name of file to save</param>
        public void SaveImgFile(string fname)
        {
            unsafe
            {
                this.HMC.SaveImgFile((sbyte*)Marshal.StringToCoTaskMemAnsi(fname));
            }
        }

        /// <summary>
        /// Open ASCII format PPM file and store it as new added attractor.
        /// </summary>
        /// <param name="fname">Name of file to open</param>
        public void OpenImgFile(string fname)
        {
            unsafe
            {
                this.HMC.OpenImgFile((sbyte*)Marshal.StringToCoTaskMemAnsi(fname), this.Colorized);
            }
        }

        /// <summary>
        /// Load attracotr form table to output vector of net.
        /// </summary>
        /// <param name="k">Number of attractor</param>
        public void LoadKtoY(int k)
        {
            unsafe
            {
                this.HMC.LoadKtoY(k);
            }
        }

        /// <summary>
        /// Print net values to standard output.
        /// </summary>
        public void PrintHN()
        {
            unsafe
            {
                this.HMC.PrintHN();
            }
        }

        /// <summary>
        /// Randomize part of output vector of net.
        /// </summary>
        /// <param name="p">Percent of noise (0, 100)</param>
        public void RandY(int p)
        {
            unsafe
            {
                this.HMC.RandY(p);
            }
        }

        /// <summary>
        /// Add new empty attractor.
        /// </summary>
        public void AddAttractor()
        {
            unsafe
            {
                this.HMC.AddAttr();
            }
        }

        /// <summary>
        /// Delete an attractor.
        /// </summary>
        /// <param name="k">Number of attractor</param>
        public void DelAttractor(int k)
        {
            unsafe
            {
                this.HMC.DelAttr(k);
            }
        }

        /// <summary>
        /// Set doubled resolution values of neuron output.
        /// </summary>
        /// <param name="newd">New value of doubled resolution</param>
        public void Setd(int newd)
        {
            unsafe
            {
                this.HMC.Setd(newd);
            }
        }

        /// <summary>
        /// Set learning constant value.
        /// </summary>
        /// <param name="neweta">New value should be (0.7, 0.9)</param>
        public void Seteta(float neweta)
        {
            unsafe
            {
                this.HMC.Seteta(neweta);
            }
        }

        /// <summary>
        /// Lern process to automatically to MSEmin of weights.
        /// </summary>
        /// <param name="arg">Kind (h - Hebbian, d - Recursive fast, z - Recursive slow, p - for Predictor)</param>
        public void LearnHN(string arg)
        {
            unsafe
            {
                this.HMC.LearnHN((sbyte*)Marshal.StringToCoTaskMemAnsi(arg));
            }
        }

        /// <summary>
        /// Lern process one step.
        /// </summary>
        /// <param name="arg">Kind (h - Hebbian, d - Recursive fast, z - Recursive slow, p - for Predictor)</param>
        /// <returns>MSE of weights</returns>
        public float LearnStepHN(string arg)
        {
            unsafe
            {
                return this.HMC.LearnStepHN((sbyte*)Marshal.StringToCoTaskMemAnsi(arg));
            }
        }

        /// <summary>
        /// One step of simulation.
        /// </summary>
        /// <param name="arg">Kind (a - Asynchronically, s - Synchronically, r - Randomlly)</param>
        /// <returns>MSE of output vector</returns>
        public float CompHN(string arg)
        {
            unsafe
            {
                return this.HMC.CompHN((sbyte*)Marshal.StringToCoTaskMemAnsi(arg));
            }
        }

        /// <summary>
        /// Set 0 values to all weights.
        /// </summary>
        public void ClearW()
        {
            unsafe
            {
                this.HMC.ClearW();
            }
        }

        /// <summary>
        /// Add attractor to net.
        /// </summary>
        /// <param name="pvY">Array of learn values, of it lenght should be equal n</param>
        public void AddAttractor(float[] pvY)
        {
            unsafe
            {                
                int n = this.HMC.Getn();
                float* pY = this.HMC.GetY();
                for (int i = 0; i < n; i++)
                {
                    float v = pvY[i];
                    pY[i] = v;
                }
                this.HMC.AddAttr();
            }
        }

        /// <summary>
        /// Set net input vecttor.
        /// </summary>
        /// <param name="pvY">Array of input values, of it lenght should be equal n</param>
        public void SetY(float[] pvY)
        {
            unsafe
            {
                int n = this.HMC.Getn();
                float* pY = stackalloc float[n];
                for (int i = 0; i < n; i++)
                {
                    pY[i] = pvY[i];
                }

                this.HMC.SetY(pY);
            }
        }

        /// <summary>
        /// Add attractor for prediction process.
        /// </summary>
        /// <param name="pvY">Array of learn values, of it lenght should be equal n</param>
        public void AddPredictorAttractor(float[] pvY)
        {
            unsafe
            {
                this.HMC.AddAttr();

                int l = this.HMC.Getl();
                float* pL = this.HMC.GetL()[l - 1];
                float* pY = this.HMC.GetY();
                int n = this.HMC.Getn();

                for (int i = 0; i < n; i++)
                {
                    float v = pvY[i];
                    pL[i] = v;
                    pY[i] = v;
                }
            }
        }

        /// <summary>
        /// Create empty Bitmap for output vector.
        /// </summary>
        /// <returns>Bitmap for output vector</returns>
        public Bitmap CreateBmp()
        {
            return this.CreateBmp(0, 0);
        }

        /// <summary>
        /// Create empty Bitmap for output vector. If both parameters equal 0 measure is detected.
        /// </summary>
        /// <param name="w">Width of bitmap</param>
        /// <param name="h">Height of bitmap</param>
        /// <returns>Bitmap for output vector</returns>
        public Bitmap CreateBmp(int w, int h)
        {
            if (w == 0 && h == 0)
            {
                return new Bitmap(this.Cols, this.Rows);
            }

            return new Bitmap(w, h);
        }

        /// <summary>
        /// Convert Float to Color, it is mean integer value between 0 and 255.
        /// </summary>
        /// <param name="f">Value to convert</param>
        /// <returns>Color converted</returns>
        public int GetColorF(float f)
        {
            float v = (f + 1.0f) / 2.0f;
            return (int)(v * 255);
        }

        /// <summary>
        /// Convert Color to Float value, Color it is mean value beetwen 0 and 255.
        /// </summary>
        /// <param name="rorGorB">Value to convert</param>
        /// <returns>Float converted</returns>
        public float GetFColor(int rorGorB)
        {
            float v = 1.0f * rorGorB / 255;
            return (v * 2.0f) - 1.0f;
        }

        /// <summary>
        /// Convert Float to attractor value for predictor.
        /// </summary>
        /// <param name="f">Value to convert</param>
        /// <returns>Attracotr value for predictor</returns>
        public int GetValueF(float f)
        {
            float v = (f + 1.0f) / 2.0f;
            return (int)(v * this.D);
        }

        /// <summary>
        /// Convert attractor value to Float for predictor.
        /// </summary>
        /// <param name="val">Value to convert</param>
        /// <returns>Float value converted</returns>
        public float GetFValue(int val)
        {
            float v = 1.0f * val / this.D;
            return (v * 2.0f) - 1.0f;
        }

        /// <summary>
        /// Draw bitmap form net output vector.
        /// </summary>
        /// <param name="bitmap">Bitmap to draw</param>
        public void DrawTextureUsingPointers(Bitmap bitmap)
        {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            BitmapData data = bitmap.LockBits(
                Rectangle.Round(bitmap.GetBounds(ref unit)),
                ImageLockMode.WriteOnly,
                PixelFormat.Format32bppArgb);
            unsafe
            {
                byte* scan0 = (byte*)data.Scan0.ToPointer();
                float* vY = this.HMC.GetY();
                for (int y = 0; y < data.Height; y++)
                {
                    byte* bits = scan0 + (y * data.Stride);
                    for (int x = 0; x < data.Width; x++)
                    {
                        if (true == this.Colorized)
                        {
                            byte color = (byte)this.GetColorF(*vY);
                            *(bits++) = color; // B
                            vY++;
                            color = (byte)this.GetColorF(*vY);
                            *(bits++) = color; // G
                            vY++;
                            color = (byte)this.GetColorF(*vY);
                            vY++;
                            *(bits++) = color; // R
                            *(bits++) = 255;   // A                            
                        }
                        else
                        {
                            byte color = (byte)this.GetColorF(*vY);
                            *(bits++) = color; // B
                            *(bits++) = color; // G                            
                            *(bits++) = color; // R
                            *(bits++) = 255;   // A
                            vY++;
                        }
                    }
                }
            }

            bitmap.UnlockBits(data);
        }
    }
}
